package reactor;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @ClassName EchoServerReactor
 * @Description reactor server demo 反应器
 * @Author lee ji/liang
 * @DateTime 2021/9/7 10:08
 * @Version 1.0
 */
public class EchoServerReactor implements Runnable {

    Selector selector;
    ServerSocketChannel serverSocketChannel;

    public EchoServerReactor() throws IOException {
        // 初始化选择器, 选择器用于channel注册, 并监听channel的就绪事件
        selector = Selector.open();
        // 初始化serverSocket, 用于和客户端建立连接
        serverSocketChannel = ServerSocketChannel.open();
        // 配置channel 为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // channel监听指定端口
        serverSocketChannel.bind(new InetSocketAddress(9000));

        // channel注册到选择器中, 监听channel的 新连接事件. 返回selectKey,这个key和channel绑定.
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 在注册事件返回的selectKey中添加附件(用于处理感兴趣的事件), 例如上面的accpet事件,
        // 如果select 发现 server channel发生了新连接事件, 那么新连接事件就由当前的附件来处理这个事件
        selectionKey.attach(new AcceptorHandler());
    }


    @Override
    public void run() {
        try {
            // 启动服务实例, 当前线程如果没有被标记中断, 就一直运行下去,
            while (!Thread.interrupted()) {
                // 选择器查询是否有已注册channel有事件就绪, 如果没有就一直阻塞在这里, 如果有就返回就绪事件的集合
                System.out.println("选择器等待io有就绪事件发生");
                selector.select();
                System.out.println("有就绪事件了");
                // 上面的方法通过, 说明已经有就绪事件了, 可以开始处理已就绪事件了. 首先拿到就绪事件的集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 拿到集合的迭代器
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                // 通过迭代器循环集合
                while (iterator.hasNext()) {
                    // 获取选择键
                    SelectionKey next = iterator.next();
                    // 从选择键中拿到通过attach方法添加的附件.
                    Runnable attachment = (Runnable) next.attachment();
                    // 如果附件不为空, 就调用附件的run方法, 直接处理就绪事件
                    if (attachment != null) {
                        // 在本实例中, 有两个附件类: AcceptorHandler 和 EchoHandler, 由于EchoHandler的处理依赖于
                        // AcceptorHandler, 所以在构造server的时候, 只添加了AcceptorHandler 附件. 所以这里最开始运行的
                        // 一定是AcceptorHandler 的run方法.
                        // 执行过accept之后, 就有可能是客户端发送数据过来了, 这时的attachment就是EchoHandler实例了
                        // 还有一种就绪事件是socketChannel的写就绪事件, 说明这时可以给客户端发送数据了
                        attachment.run();
                    }
                }
                // 处理完成后, 清除.
                selectionKeys.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    class AcceptorHandler implements Runnable {
        @Override
        public void run() {
            try {
                // 方法执行到这里说明, 连接事件已经就绪, 可以建立连接套接字用于后续的输入输出通讯了.
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 获取到安连接套接字后, 通过EchoHandler实例来进行输入输出的实现.
                if (socketChannel != null) {
                    System.out.println("连接建立完成");
                    new EchoHandler(socketChannel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    class EchoHandler implements Runnable {

        SocketChannel socketChannel;
        SelectionKey selectionKey;
        int RECEIVING = 0, SENDING = 1;
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 一开始, 设置为接收模式, 应为一开始必然是客户端先发送数据给服务端
        int state = RECEIVING;

        public EchoHandler(SocketChannel socketChannel) throws IOException {
            // 初始化socketChannel,
            this.socketChannel = socketChannel;

//			socketChannel.finishConnect();
            // 配置channel为非阻塞channel
            socketChannel.configureBlocking(false);
            // 然后把channel注册到全局选择器中. 并返回响应的选择键
            selectionKey = socketChannel.register(selector, 0);
            // 将处理channel就绪事件的对象, 也就是EchoHandler实例绑定到选择键上
            selectionKey.attach(this);
            // 告诉选择器, 需要关注的是socketChannel的 可读事件, 说明有客户端发送数据到服务端了.
            selectionKey.interestOps(SelectionKey.OP_READ);

            // 还不知道是啥意思
//			selector.wakeup();
        }

        @Override
        public void run() {
            try {
                // 说明有客户端发送数据到服务端, 需要接收.
                if (state == RECEIVING) {
                    int length = 0;
                    while ((length = socketChannel.read(byteBuffer)) > 0) { // socketChannel把数据从用户空间读到指定的
                        // byteBuffer中. 然后打印数据, 循环读取打印
                        System.out.println(new String(byteBuffer.array(), 0, length));
                    }
                    // 读完了以后, 翻转byteBuffer的状态, 从读模式切换到写模式
                    byteBuffer.flip();
                    // 然后把关注事件修改为 可写.
                    selectionKey.interestOps(SelectionKey.OP_WRITE);
                    // 然后把当前实例的状态修改为需要给客户端发送数据.
                    state = SENDING;
                    System.out.println("服务端完成了一次读数据");
                    // 可写事件就绪后
                } else if (state == SENDING) {
                    // socketChannel把数据从byteBuffer中写入到channel中, 然后通过channel把数据发送给客户端
                    socketChannel.write(byteBuffer);
                    // 发送完毕后, 清空buffer
                    byteBuffer.clear();
                    // 把socketChannel的关注事件从可写, 再次修改为可读, 等待客户端的下一次发送过来的数据
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    // 修改当前实例的状态为: 接收数据
                    state = RECEIVING;
                    System.out.println("服务端完成了一次数据回写");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) throws IOException {
        //构造Server实例,并启动服务实例.
        new EchoServerReactor().run();
    }

}
